;############################################################################ 

; Считывает исходный (сжимаемый) файл
; Аргументы:
;			file_read - указатель на строку, содержащую имя входного файла
;			mode_read - строка; режим открытия файла
; Возвращаемые значения:
;			al - результат выполнения (0 - успех, > 0 - неудача)
;			code - массив количеств вхождений каждого символа в исходный файл (256 элементов по 4-е байта)
;			[tree_size] - число различных символов, входящий в файл
; Комментарий:
;			Т.к. элементы в code ограничены 4-мя байтами, то размер исходного файла ограничен 2^32-1 = 4294967295
;			байтами (т.е. ~4 гигабайта)

read_input_file:

	push ebp
	mov ebp,esp

	; Открываем файл на чтение
	push mode_read
	push dword [file_read]
	call fopen
	test eax,eax
	jnz RIF_open_read_no_error
		; Ошибка - скорее всего файл не существует
		mov al,RIF_open_read_error
		jmp short RIF_end
	RIF_open_read_no_error:

	; Дескриптор файла сохраняем в edx
	mov edx,eax
	; Указатель на буфер сохраняем в esi
	mov esi,i_buffer

	RIF_next_page:
		; Считываем очередную порцию информации
		push edx
		push dword io_buffer_size
		push dword 1
		push esi
		call fread

		add esp,dword 12
		pop edx

		; Количество прочитанных символов дублируем в ecx
		mov ecx,eax

		; В случае, если было прочитано меньше одного символа - завершить подсчет количеств вхождений
		cmp eax,1
		jl RIF_too_many_symb
			mov edi,esi
			RIF_next_symb:
				; Определяем код очередного символа
				movzx ebx,byte [edi]
				cmp [code+4*ebx],dword 0
				jnz RIF_symb_found
					; Если символ еще не встречался - увеличим на единицу размер массива количеств вхождений
					inc word [tree_size]
				RIF_symb_found:
					; Увеличим на единицу количество вхождений символа
					inc dword [code+4*ebx]
				; Перейдем к новому символу
				inc edi
			loop RIF_next_symb
		
			; Если было считано символов меньше, чем размер буфера, то значит, что достигнут конец файла
			cmp eax,dword io_buffer_size
			jl RIF_eof
	jmp short RIF_next_page

	RIF_too_many_symb:

	; Ни один символ не был считан - это может означать как конец файла, так и ошибку
	; Проверим, возникла ли ошибка
	push edx
	call ferror
	pop edx
	test eax,eax
	jz RIF_eof
		; Да, при чтении возникла ошибка
		mov al,RIF_read_error
		jmp short RIF_close
	RIF_eof:

	xor al,al

	RIF_close:

	push eax

	; Закрываем файл
	push edx
	call fclose
	test eax,eax
	jz RIF_close_read_no_error
		; Ошибка при закрытии файла
		mov al,RIF_close_read_error
		jmp short RIF_end
	RIF_close_read_no_error:

	pop eax
	pop eax

	RIF_end:

	mov esp,ebp
	pop ebp

	ret

;############################################################################ 

; Запись количества различных символов в исходном файле, кодов символов и количество вхождений символов,
; встретившегося хотя бы раз в исходном файле, в результирующий файл
; Аргумент:
;			[esp + 4] - FILE* результирующего файла
;			code - массив количеств вхождений символов
; Возвращаемое значение:
;			al - результат выполнения (0 - успех, > 0 - неудача)
;			результирующий файл - в его начале расположена таблица вхождений каждого встретившегося хотя бы раз
;								  символа
; Комментарий:
;			В файл записываем: количество различных символов в исходном файле (2 байта), код символа (1 байт)
;			и количество вхождений символа (4-е байта) для каждого символа, который хотя бы раз встречается в файле

put_table_symbol_enters_in_file:

	; Сохраняем данные о стеке
	push ebp
	mov ebp,esp

	; В esi адрес буферного пространства для операции записи
	mov esi,o_buffer

	; Количество_различных_символов = (tree_size + 1) / 2
	mov cx,[tree_size]
	inc cx
	shr cx,1
	and cx,1FFh

	; Запишем количество различных символов, встретившихся хотя бы раз в исходном файле
	mov [esi],cx
	mov edi,2

	; ebx будет адресовать массив количеств вхождений символов
	mov ebx,code
	; ecx = 256 (размер массива)
	mov ecx,100h

	PT_next_enter:
		; Считываем количества вхождений очередного символа
		mov edx,[ebx]

		test edx,edx
		jz PT_enter_not_found
			; Если очередной символ хотя бы раз встретился в файле, то запишем данные о нем в результирующий
			; файл

			mov ax,cx

			; Вычислим код символа. код_символа = | ax - 256 |, при этом известно, что 0 < ax <= 256
			sub ax,100h
			neg ax

			; Запишем код символа в файл
			mov [esi + edi],al
			inc edi

			; Запишем количество вхождений символа в исходный файл (4-е байта)
			; Для простоты считывания при декомпрессии будем записывать байты в обратной последовательности
			; (т.о. в результирующем файле: байту с большим смещением соответствует младший байт)
			bswap edx
			mov [esi + edi],edx
			add edi,4
		PT_enter_not_found:
		
		; Перейдем к количество вхождений следующего символа
		add ebx,4
	loop PT_next_enter

	; Считываем дескриптор файла
	push dword [ebp + 8]
	push dword edi
	push dword 1
	push esi
	call fwrite

	; Проверим, не выполнился ли fwrite с ошибкой - для этого сравним количество записанных
	; байт с тем количеством байт, которое было передано в буфере
	cmp [esp + 8],eax
	jz PT_write_no_error
		mov al,PT_write_error
		jmp short PT_end
	PT_write_no_error:

	xor al,al
	
	PT_end:

	; Восстанавливаем стек
	mov esp,ebp
	pop ebp
	
	ret

;############################################################################ 

; Непосредственное сжатие исходного файла и запись результатов в выходной файл
; Аргумент:
;			file_read - указатель на имя исходного файла
;			[esp + 4] - FILE* результирующего файла
; Возвращаемое значение:
;			al - результат выполнения (0 - успех, > 0 - неудача)
;			результирующий файл - после таблицы количеств вхождений расположено собственно сжатое содержимое
;			исходного файла

compress_file:
	
	push ebp
	mov ebp,esp

	; Исходный файл открываем на чтение
	push mode_read
	push dword [file_read]
	call fopen
	test eax,eax
	jnz CF_open_read_no_error
		mov al,CF_open_read_error
		jmp CF_end_no_close_read
	CF_open_read_no_error:

	add esp,8

	; Переменные на стеке, хранящие дескрипторы исходного и результирующего файлов
	%define i_file [ebp - 4]
	%define o_file [ebp + 8]

	; Непосредственное создание переменной, хранящей дескриптор исходного файла
	push eax

	; edi - количество значимых байт в результирующем буфере (o_buffer)
	xor edi,edi
	; Обнуляем буферный регистр
	; В буферном регистре накапливаются очередные четыре байта, перед
	; их отправкой в результирующий буфер
	xor ebx,ebx
	; Размер (в битах) значимой части в буферном регистре (ebx)
	xor ch,ch

	CF_next_page:
		push ecx

		; Считаем очередную порцию исходных данных во входной буфер
		push dword i_file
		push dword io_buffer_size
		push dword 1
		push dword i_buffer
		call fread
		add esp,12

		cmp eax,1
		jnl CF_read_no_error
			; Иначе - проверим на ошибку
			; (если ничего не было считано - либо eof, либо ошибка)
			call ferror
			test eax,eax
			jnz CF_really_read_error
				; Достигнут eof - завершим цикл считывания исходного файла
				pop ecx
				pop ecx
				jmp CF_end_loop
			CF_really_read_error:
			; При чтении произошла ошибка - вернем код ошибки
			mov al,CF_read_error
			jmp CF_end
		CF_read_no_error:
		
		pop ecx
		pop ecx

		; Если считан хотя бы один байт - будем его обрабатывать
		
		; Сохраним количество считанных байт
		push eax

		; esi - текущее смещение в буфере с исходными данными (i_buffer)
		xor esi,esi
		
		CF_next_symb:
			; Последовательно обработаем каждый байт из буфера с исходными данными

			; Считаем очередной байт
			movzx eax,byte [i_buffer + esi]
			; cl - количество бит в коде обрабатываемого байта
			mov cl,[num_bit + eax]
			; edx - код обрабатываемого байта
			mov edx,[code + 4*eax]
			
			; Проверим, поместится ли целиком код обрабатываемого байта в буферный регистр
			mov al,cl
			add al,ch
			cmp al,32
			jnl CF_flush
				; Да, поместится
				; Сдвинем влево буферный регистр на размер кода
				shl ebx,cl
				; Запишем код в буферный регистр
				add ebx,edx
				; Увеличим счетчик значимых битов буферного регистра
				add ch,cl
				jmp short CF_endif
			CF_flush:
				; Нет, не поместится
				; Вычисляем, сколько битов мы еще можем записать в буферный регистр
				mov ah,32
				sub ah,ch
				; (Количество бит в коде + количество бит в буферном регистре до
				; помещения в него текущего кода) сохраним в промежуточном регистре
				mov ch,al
				; Сдвинем влево буферный регистр на нужное количество бит
				mov cl,ah
				shl ebx,cl
				; В промежуточный регистр (eax) запишем код байта
				mov eax,edx
				; Вычислим количество битов из текущего кода, не поместившихся в буферный регистр
				sub ch,32
				; Уберем непоместившиеся биты из кода обрабатываемого байта с помощью сдвига вправо
				mov cl,ch
				shr eax,cl
				; Дополним буферный регистр
				add ebx,eax
				; Поменяем порядок следования байтов в буферном регистре
				; (требуется для более простого алгоритма декомпрессии)
				bswap ebx
				; Поместим значение буферного регистра в результирующий буфер
				mov [o_buffer + edi],ebx
				; Увеличим размер значимой части результирующего буфера на 4
				add edi,4
				; Поместим код обрабатываемого байта в буферный регистр
				; (значение ch - счетчика количества значимых бит в буферном регистре уже
				; установлено в значение, равное количеству бит, не поместившихся в результирующий
				; буфер)
				mov ebx,edx
			CF_endif:

			; Проверим, не заполнен ли результирующий буфер
			cmp edi,io_buffer_size
			jnz CF_o_buffer_not_fill
				; Если заполнен - сбросим его содержимое в результирующий файл
				push ecx
				
				push dword o_file
				push dword io_buffer_size
				push dword 1
				push dword o_buffer
				call fwrite

				; Проверим, не выполнился ли fwrite с ошибкой - для этого сравним количество записанных
				; байт с тем количеством байт, которое было передано в буфере
				cmp eax,io_buffer_size
				jz CF_write_no_error_1
					mov al,CF_write_error_1
					jmp short CF_end
				CF_write_no_error_1:

				add esp,16
				pop ecx

				; Обнулим счетчик количества значимых байт в результирующем буфере
				xor edi,edi
			CF_o_buffer_not_fill:

			; Перейдем к следующему байту в исходном буфере
			; (если, конечно, исходный буфер полностью еще не пройден)
			inc esi
		cmp esi,[esp]
		jl CF_next_symb

		; Если исходный буфер полностью пройден - считаем новую порцию информации
	jmp CF_next_page
	
	; Достигнут конец исходного файла
	CF_end_loop:

	; Сбросим остаток значимых байт из буферного регистра в результирующий буфер
	; Проверим, остались ли в буферном регистре значимые биты
	test ch,ch
	jz CF_not_flush
		; Да, остались
		; Вычислим количество байт, необходимых для хранения значимых бит
		; (округление в большую сторону)
		; ch = ch / 8 ; ch = (ch == 4) ? ch : (ch - 1)
		mov ah,ch
		mov cl,ch
		shr cl,3
		cmp cl,4
		jz CF_not_inc
			inc cl
		CF_not_inc:
		mov ch,cl
		; Вычислим количество бит в требуемом количестве байт
		; cl = cl * 8
		shl cl,3
		; Вычислим избыток требуемого количества бит по сравнению с уже
		; имеющимся в буферном регистре
		sub cl,ah
		; Дополним буферный регистр недостающим количеством бит
		shl ebx,cl

		; Поменяем порядок следования байт в буферном регистре
		bswap ebx
		; Сдвинем вправо буферный регистр, тем самым компенсируя лишние биты справа,
		; появившиеся из-за bswap'а
		mov cl,4
		sub cl,ch
		shl cl,3
		shr ebx,cl
		; Запишем содержимое буферного регистра в результирующий буфер
		mov [o_buffer + edi],ebx
		; Увеличим счетчик значимых байт в результирующем буфере на число значимых байт
		; в последней сброшенной в буфер порции данных
		movzx eax,ch
		add edi,eax
	CF_not_flush:

	; Остаток полезных данных в результирующем буфере сбросим в результирующий файл
	push dword o_file
	push dword edi
	push dword 1
	push dword o_buffer
	call fwrite
	
	add esp,16

	; Проверим, не выполнился ли fwrite с ошибкой - для этого сравним количество записанных
	; байт с тем количеством байт, которое было передано в буфере
	cmp edi,eax
	jz CF_write_no_error_2
		mov al,CF_write_error_2
		jmp short CF_end
	CF_write_no_error_2:

	; В случае успеха - вернем ноль
	xor al,al

	CF_end:

	push eax
	push dword i_file

	; Если исходный файл удалось открыть - закроем его
	call fclose
	test eax,eax
	jz CF_close_read_no_error
		mov al,CF_close_read_error
		jmp short CF_end_no_close_read
	CF_close_read_no_error:

	mov eax,[esp + 4]

	CF_end_no_close_read:

	; Восстанавливаем стек
	mov esp,ebp
	pop ebp

	ret

;############################################################################ 

